package com.mofeng.myshop.service.user.impl;


import com.mofeng.myshop.domain.po.UserBase;
import com.mofeng.myshop.domain.vo.user.UserVo;
import com.mofeng.myshop.mapper.UserBaseMapper;
import com.mofeng.myshop.service.user.QrCodeService;
import com.mofeng.myshop.service.user.UserUtilsService;
import com.mofeng.myshop.until.*;
import com.mofeng.myshop.until.component.CookieUtils;
import com.mofeng.myshop.until.component.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.TextUtils;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
@RequiredArgsConstructor
public class QrCodeServiceImpl implements QrCodeService {
    private final SnowflakeIdWorker idWorker;

    private final RedisUtil redisUtil;

    private final CookieUtils cookieUtils;

    private final UserUtilsService userUtilsService;

    private final UserBaseMapper userMapper;

    private final String LAST_REQUEST_LOGIN_ID = "last_request_loginId";
    public static final String KEY_PC_LOGIN_CODE = "key_pc_login_code_";
    private final String LOGIN_STATE_FALSE = "false";
    private final int WAITING_TIME = 30;//二维码刷新时间

    private static final Map<String, CountDownLatch> latches = new HashMap<>();

    public void onPhoneDoLogin(String loginId) {
        CountDownLatch countDownLatch = latches.get(loginId);
        if (countDownLatch != null) {
            countDownLatch.countDown();
        }
    }

    /**
     * 获取
     *
     * @param loginId
     * @return
     */
    public CountDownLatch getLatch(String loginId) {
        CountDownLatch countDownLatch = latches.get(loginId);
        if (countDownLatch == null) {
            countDownLatch = new CountDownLatch(1);
            latches.put(loginId, countDownLatch);
        }
        return countDownLatch;
    }

    /**
     * 删除
     *
     * @param loginId
     */
    public void deleteLatch(String loginId) {
        latches.remove(loginId);
    }

    /**
     * 获取登录二维码
     *
     * @return
     */
    @Override
    public R getPcLoginQrCodeInfo() {
        //尝试取出上一次的loginId
        String lastLoginId = cookieUtils.getCookie(LAST_REQUEST_LOGIN_ID );
        //if (!TextUtils.isEmpty(lastLoginId)) {
        //    //先把redis里的删除
        //    redisUtil.del(Constants.User.Redis.KEY_PC_LOGIN_CODE + lastLoginId);
        //    //检查上次的请求时间，如果太频繁，则直接返回
        //    Object lastGetTime = redisUtil.get(Constants.User.LAST_REQUEST_LOGIN_ID + lastLoginId);
        //    if (lastGetTime != null) {
        //        return R.FAILED("服务器繁忙，请稍后重试.");
        //    }
        //        }

        // 1、生成一个唯一的ID
        long code;
        if (!TextUtils.isEmpty(lastLoginId)) {
            code = Long.parseLong(lastLoginId);
        } else {
            code = idWorker.nextId();
        }
        // 2、保存到redis里，值为false，时间为5分钟（二维码的有效期）
        redisUtil.set(KEY_PC_LOGIN_CODE + code, LOGIN_STATE_FALSE, Constants.Times.SECOND * 5L);
        Map<String, Object> result = new HashMap<>();
        String originalDomain = com.mofeng.myshop.until.TextUtils.getDomain(ServletUtils.getRequest());
        result.put("code", String.valueOf(code));
        result.put("url", originalDomain + "/portal/image/qr-code/" + code);
        cookieUtils.setCookie(LAST_REQUEST_LOGIN_ID, String.valueOf(code));
        //redisUtil.set(Constants.User.LAST_REQUEST_LOGIN_ID + String.valueOf(code),
        //        "true", Constants.Times.TOO_FREQUENT_TIME);
        // 返回结果
        return R.SUCCESS("获取成功.").setData(result);
    }

    /**
     * 检查二维码登录状态
     *
     * @param loginId
     * @return 1、登录成功(LoginId对应的值为true )
     * 2、等待扫描(LoginId对应的值为false )
     * 3、二维码已经过期了 loginId 对应的值为nul4
     */
    @Override
    public R checkQrCodeLoginState(String loginId) {
        R result = checkeLoginIdState(loginId);
        if (result != null) {
            return result;
        }
        //先等待一段时间，再去检查。
        //如果超出了这个时间，就返回等待扫码。
        Callable<R> callable = new Callable<R>() {
            @Override
            public R call() throws Exception {
                try {
                    //先阻塞
                    getLatch(loginId).await(WAITING_TIME, TimeUnit.SECONDS);
                    //收到状态更新的通知，我们就检查loginId对应的状态
                    R checkResult = checkeLoginIdState(loginId);
                    if (checkResult != null) return checkResult;
                    //超时则返回等待扫描
                    return R.FAILED("等待扫描");
                } finally {
                    //完事后，删除对应的latch
                    deleteLatch(loginId);
                }
            }
        };
        try {
            return callable.call();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return R.FAILED("等待扫描");
    }

    /**
     * 更新二维码的登录状态
     *
     * @param loginId
     * @return
     */
    @Override
    public R updateQrCodeLoginState(String loginId) {
        //1、检查用户是否登录
        UserVo userVo = LoginUserThreadLocalUtil.get();
        if (userVo == null) {
            return R.FAILED("未登录");
        }
        //2、改变loginId对应的值=true
        redisUtil.set(KEY_PC_LOGIN_CODE + loginId, userVo.getId());
        //2.1、通知正在等待的扫描任务
        onPhoneDoLogin(loginId);
        //3、返回结果
        return R.SUCCESS("登录成功.");
    }

    /**
     * 检查登录状态
     *
     * @param loginId
     * @return
     */
    private R checkeLoginIdState(String loginId) {
        String loginState = (String) redisUtil.get(KEY_PC_LOGIN_CODE + loginId);
        if (loginState == null) {
            //二维码失效
            return R.FAILED("二维码失效");
        }
        if (!TextUtils.isEmpty(loginState) &&
                !LOGIN_STATE_FALSE.equals(loginState)) {
            UserBase userFromDB = userMapper.findById(MyUtil.NumberUtil.asLong(loginState));
            //创建token,也就是PC端的登示
            if (userFromDB == null) {
                return R.FAILED("二维码已过期");
            }
            String newTokenKey = userUtilsService.createToken(userFromDB, Constants.FROM_PC);
            cookieUtils.deleteCookie( LAST_REQUEST_LOGIN_ID);
            //登陆成功
            return R.SUCCESS();
        }
        return null;
    }
}
